/*
 * CCameraPreprocessor.cpp
 *
 *  Created on: 04.09.2011
 *      Author: Blinov Kirill
 *      Copyright: LGPL
 */

#include "../Constants.h"
#include "CCameraPreprocessor.h"
#include <boost/bind.hpp>
#include <SDL/SDL.h>

CCameraPreprocessor::CCameraPreprocessor():
	ProcessingLoopControl(false), frameQueue(NULL)
{
	memset( previousMouseButtonState, MOUSE_KEY_UP, sizeof(previousMouseButtonState) );
}

CCameraPreprocessor::~CCameraPreprocessor()
{
	Stop();
}

bool
CCameraPreprocessor::Start( TThreadSafeQueue<FULL_FRAME_DATA*> *FrameQueue, TThreadSafeQueue<INPUT_MESSAGE*> *InputMessageQueue, CRenderer *Renderer )
{
	bool Result = false;
	if ( false == ProcessingLoopControl )
	{
		if ( FrameQueue != NULL )
		{
			frameQueue = FrameQueue;
		}
		if ( InputMessageQueue != NULL )
		{
			inputQueue = InputMessageQueue;
		}

		if ( ( NULL != frameQueue ) && ( NULL != inputQueue ) )
		{
			ProcessingLoopControl = true;
			ProcessingThread( Renderer ); //Run thread to process frames
			Result = true;
		}
	}
	return Result;
}

void
CCameraPreprocessor::Stop()
{
	if ( ProcessingLoopControl )
	{
		ProcessingLoopControl = false;
		//TODO Возможно нужна синхронизация с потоком ProcessingThread
	}
}

void
CCameraPreprocessor::ProcessingThread( CRenderer *Renderer )
{
	while ( ProcessingLoopControl )
	{
		ProcessInput(); // At first read messages from system to transfer them to GUI

		FULL_FRAME_DATA *FrameData;

		if ( frameQueue->Receive( &FrameData ) )
		{
			if ( (FrameData != NULL) && Renderer->StartNewFrame( FrameData->vertexArrayId ) )//Can we start a new frame
			{
				for  ( std::vector<GAME_CAMERA*>::iterator CameraIt = FrameData->camerasArray.begin(), //Cameras loop
						CameraEnd = FrameData->camerasArray.end();
						CameraIt != CameraEnd; ++CameraIt )
				{
					GAME_CAMERA *CurrentCamera = *CameraIt.base();
					std::vector<GAME_OBJECT*> GameObjectsToRender;

					//Fill data for objects cutting
					memcpy( &cameraSphereCenter[0], &CurrentCamera->sphere[0], sizeof( glm::vec3 ) );
					cameraSphereRadius = CurrentCamera->sphere.w;
					memcpy( glm::value_ptr( frustum[0] ), glm::value_ptr( CurrentCamera->frustum[0] ), ( sizeof(glm::vec4) * PARALLELEPIPED_PLANE_COUNT ) );

					for ( std::vector<GAME_OBJECT*>::iterator it = FrameData->gameObjectsArray.begin(), // Objects loop
							end = FrameData->gameObjectsArray.end();
							it != end; ++it )
					{
						GAME_OBJECT *TmpGameObject = *it.base();

						//if ( CutObjectByViewSphere( TmpGameObject->sphereCenter, TmpGameObject->sphereRadius ) )
						{
							float DistanceToCamera = CutObjectSphereByViewFrustum( TmpGameObject->sphereCenter, TmpGameObject->sphereRadius );
							//if ( DistanceToCamera > 0.0f )
							{
								TmpGameObject->distanceToCamera = DistanceToCamera;
								GameObjectsToRender.push_back( TmpGameObject );
							}
						}

					}

					std::sort( GameObjectsToRender.begin(), GameObjectsToRender.end(), &CompareGameObjectsByDistance ); //Sort objects by distance
					Renderer->RenderCameraFrame( GameObjectsToRender, (*CurrentCamera) );

					delete CurrentCamera;
				}
				Renderer->RenderFinalFrame( FrameData->guiElementsArray, FrameData->guiDummy );
			}

			delete FrameData;
		}
	}
}

bool
CCameraPreprocessor::CutObjectByViewSphere( const glm::vec3 &SphereCenter, const float Radius )
{
	return ( glm::length( SphereCenter - cameraSphereCenter ) < ( Radius + cameraSphereRadius ) );
}

float
CCameraPreprocessor::CutObjectSphereByViewFrustum( const glm::vec3 &SphereCenter, const float Radius )
{
    float Distance;
	for( UINT i = 0; i < PARALLELEPIPED_PLANE_COUNT; i++ )
	{
	   Distance = frustum[i].x * SphereCenter.x + frustum[i].y * SphereCenter.y + frustum[i].z * SphereCenter.z +
			      frustum[i].w;

	   if( Distance <= -Radius )
	   {
	      return 0;
	   }
	}

	return ( Distance + Radius );
}

bool
CCameraPreprocessor::CutObjectBoxByViewFrustum( const glm::vec3 &FirstCorner, const glm::vec3 &SecondCorner )
{
	return true; //TODO Закодить отсечение по габаритному контейнеру
}

USHORT
CCameraPreprocessor::GetMouseButtonFromSDL( const USHORT SdlButton )
{
    USHORT Result = -1;
	if ( SDL_BUTTON_LEFT == SdlButton )
	{
		Result = MOUSE_BUTTON_LEFT;
    }
	else if ( SDL_BUTTON_MIDDLE == SdlButton )
	{
		Result = MOUSE_BUTTON_MIDDLE;
	}
	else if ( SDL_BUTTON_RIGHT == SdlButton )
	{
		Result = MOUSE_BUTTON_RIGHT;
	}

	return Result;
}

void
CCameraPreprocessor::ProcessInput()
{
    SDL_Event event;
	INPUT_MESSAGE* NewMessage = new INPUT_MESSAGE(); //Message must be deleted by reciver
	bool MessageFilled = false;

	while ( SDL_PollEvent ( &event ) )
	{
		switch ( event.type )
		{
		   break;case SDL_QUIT:
		   {
			   NewMessage->terminateApplication = true;
			   MessageFilled = true;
		   }
		   break;case SDL_KEYDOWN:
           {
        	   NewMessage->keys.push_back( event.key.keysym.sym );
        	   MessageFilled = true;
           }
       	   break;case SDL_MOUSEMOTION:
           {
        	   NewMessage->mouseDelta.x += event.motion.xrel;
        	   NewMessage->mouseDelta.y += event.motion.yrel;

        	   MessageFilled = true;
           }
           break;case SDL_MOUSEBUTTONDOWN:
           {
        	   USHORT Button = GetMouseButtonFromSDL( event.button.button );

			   if ( Button < MOUSE_KEY_COUNT )
			   {
				   previousMouseButtonState[ Button ] = MOUSE_KEY_DOWN;
				   MessageFilled = true;
			   }
           }
           break;case SDL_MOUSEBUTTONUP:
           {
        	   USHORT Button = GetMouseButtonFromSDL( event.button.button );

        	   if ( Button < MOUSE_KEY_COUNT )
        	   {
				   previousMouseButtonState[ Button ] = MOUSE_KEY_UP;
        		   MessageFilled = true;
        	   }
           }
           break;
		}
	}

	if ( MessageFilled )
	{
 	    NewMessage->mousePosition.x = event.motion.x; //Delete this when start using animated cursor
 	    NewMessage->mousePosition.y = event.motion.y;

		memcpy( NewMessage->mouseKeys, previousMouseButtonState, sizeof(USHORT) * MOUSE_KEY_COUNT );
		inputQueue->Send( NewMessage );
	}
	else
	{
		delete NewMessage;
	}
}
